/* PTP master selection test.  This test case validates,
   1. Event master will be triggered when different utc offset is announced.
   2. Master clock port identity must be same during sync.
   3. Master clock port can be changed after timeout.
*/

#include   "netx_ptp_utility.h"

#define     DEMO_STACK_SIZE    2048

/* Define the ThreadX and NetX object control blocks...  */

static TX_THREAD               ntest_0;
static TX_THREAD               ntest_1;

static NX_PACKET_POOL          pool_0;
static NX_IP                   ip_0;
static NX_IP                   ip_1;
static NX_UDP_SOCKET           generic_socket;
static NX_UDP_SOCKET           event_socket;
static NX_PTP_CLIENT           ptp_client;

static NX_PTP_TIME             sync_ts = {0x0, 0x5F97CD71, 0x240d93b2};
static NX_PTP_TIME             follow_up_ts = {0x0, 0x5F97CD71, 0x240e29e0};
static NX_PTP_TIME             delay_response_ts = {0x0, 0x5F97CD71, 0x2494b730};
static NX_PTP_TIME             synced_time;
static NX_PTP_TIME             expected_time;
static USHORT                  synced_utc_offset;
static USHORT                  expected_utc_offset;
static UCHAR                   ptp_stack[2048];


#define NUM_PACKETS            24
#define PACKET_SIZE            1536
#define PACKET_POOL_SIZE       (NUM_PACKETS * (PACKET_SIZE + sizeof(NX_PACKET)))

/* Define thread prototypes.  */

static void    ntest_0_entry(ULONG thread_input);
static void    ntest_1_entry(ULONG thread_input);
extern void    _nx_ram_network_driver(struct NX_IP_DRIVER_STRUCT *driver_req);


/* Define what the initial system looks like.  */
#ifdef CTEST
VOID test_application_define(void *first_unused_memory)
#else
void netx_ptp_client_basic_application_define(void *first_unused_memory)
#endif
{
CHAR       *pointer;
UINT       status;

    /* Print out test information banner.  */
    printf("NetX Test:   PTP Master Selection Test ................................");

#if defined(NX_ENABLE_GPTP) || (NX_PTP_CLIENT_TRANSPORT_UDP==0)
    printf("N/A\n");
    test_control_return(3);
#else 
    /* Setup the working pointer.  */
    pointer = (CHAR *) first_unused_memory;

    /* Create the main thread.  */
    tx_thread_create(&ntest_0, "thread 0", ntest_0_entry, 0,
                     pointer, DEMO_STACK_SIZE, 
                     4, 4, TX_NO_TIME_SLICE, TX_AUTO_START);

    pointer = pointer + DEMO_STACK_SIZE;

    /* Create the main thread.  */
    tx_thread_create(&ntest_1, "thread 1", ntest_1_entry, 0,
                     pointer, DEMO_STACK_SIZE, 
                     3, 3, TX_NO_TIME_SLICE, TX_AUTO_START);

    pointer = pointer + DEMO_STACK_SIZE;

    /* Initialize the NetX system.  */
    nx_system_initialize();

    /* Create a packet pool.  */
    status = nx_packet_pool_create(&pool_0, "NetX Main Packet Pool", PACKET_SIZE, pointer, PACKET_POOL_SIZE);
    pointer = pointer + PACKET_POOL_SIZE;

    if(status)
        ASSERT_SUCCESS(status);

    /* Create an IP instance.  */
    status = nx_ip_create(&ip_0, "NetX IP Instance 0", IP_ADDRESS(1, 2, 3, 4), 0xFFFFFF00UL, &pool_0, _nx_ram_network_driver,
                          pointer, 2048, 1);
    pointer = pointer + 2048;

    /* Create another IP instance.  */
    status += nx_ip_create(&ip_1, "NetX IP Instance 1", IP_ADDRESS(1, 2, 3, 5), 0xFFFFFF00UL, &pool_0, _nx_ram_network_driver,
                           pointer, 2048, 1);
    pointer = pointer + 2048;

    if(status)
        ASSERT_SUCCESS(status);

    /* Enable ARP and supply ARP cache memory for IP Instance 0.  */
    status = nx_arp_enable(&ip_0, (void *) pointer, 1024);
    pointer = pointer + 1024;

    /* Enable ARP and supply ARP cache memory for IP Instance 1.  */
    status += nx_arp_enable(&ip_1, (void *) pointer, 1024);
    pointer = pointer + 1024;

    /* Check ARP enable status.  */
    if(status)
        ASSERT_SUCCESS(status);

    /* Enable UDP processing for both IP instances.  */
    status = nx_udp_enable(&ip_0);
    status += nx_udp_enable(&ip_1);

    /* Check UDP enable status.  */
    if(status)
        ASSERT_SUCCESS(status);
#endif
}


/* PTP handler.  */
static UINT ptp_event_callback(NX_PTP_CLIENT *ptp_client_ptr, UINT event, VOID *event_data, VOID *callback_data)
{
NX_PTP_DATE_TIME date;
    
    NX_PARAMETER_NOT_USED(callback_data);

    switch (event)
    {
        case NX_PTP_CLIENT_EVENT_MASTER:
        {
            DBGPRINTF("new MASTER clock!\r\n");
            break;
        }

        case NX_PTP_CLIENT_EVENT_SYNC:
        {
            nx_ptp_client_sync_info_get((NX_PTP_CLIENT_SYNC *)event_data, NX_NULL, &synced_utc_offset);
            DBGPRINTF("SYNC event: utc offset=%d\r\n", synced_utc_offset);

            /* read the PTP clock */
            nx_ptp_client_time_get(ptp_client_ptr, &synced_time);

            /* convert PTP time to UTC date and time */
            nx_ptp_client_utility_convert_time_to_date(&synced_time, -synced_utc_offset, &date);

            /* display the current time */
            DBGPRINTF("ts: %d%d.%d\r\n", synced_time.second_high,
                                         synced_time.second_low,
                                         synced_time.nanosecond);
            DBGPRINTF("%2u/%02u/%u %02u:%02u:%02u.%09lu\r\n", date.day, date.month, date.year,
                                                              date.hour, date.minute, date.second,
                                                              date.nanosecond);

            break;
        }

        case NX_PTP_CLIENT_EVENT_TIMEOUT:
        {
            DBGPRINTF("Master clock TIMEOUT!\r\n");
            break;
        }
        default:
        {
            break;
        }
    }

    return(0);
}

/* Define the test threads.  */
static void    ntest_0_entry(ULONG thread_input)
{
UINT status;
UINT i;
NX_PTP_TIME ts_zero = {0};
UINT timeout_ticks;

    /* Set expected value. */
    expected_utc_offset = 0x1234;
    expected_time.second_high = (delay_response_ts.second_high + follow_up_ts.second_high) / 2;
    expected_time.second_low = (delay_response_ts.second_low + follow_up_ts.second_low) / 2;
    expected_time.nanosecond = (delay_response_ts.nanosecond + follow_up_ts.nanosecond) / 2;
    
    /* Create the PTP client instance */
    status = nx_ptp_client_create(&ptp_client, &ip_0, 0, &pool_0,
                                  2, ptp_stack, sizeof(ptp_stack),
                                  nx_ptp_client_soft_clock_callback, NX_NULL);
    ASSERT_SUCCESS(status);

    for (i = 0; i < 3; i++)
    {

        /* Reset synced time. */
        memset(&synced_time, 0, sizeof(synced_time));
        synced_utc_offset = 0xFFFF;

        if (i < 2)
        {
            
            /* start the PTP client */
            status = nx_ptp_client_start(&ptp_client, NX_NULL, 0, 0, 0, ptp_event_callback, NX_NULL);
            ASSERT_SUCCESS(status);
        }

        /* Reset synced time. */
        memset(&synced_time, 0, sizeof(synced_time));
        synced_utc_offset = 0xFFFF;
        
        /* Wake up server thread.  */
        tx_thread_resume(&ntest_1);

        /* Sleep 5 seconds for sync up. */
        tx_thread_sleep(5 * NX_IP_PERIODIC_RATE);

        /* Compare synced time. */
        if (i == 1)
        {

            /* Time not synched. */
            ASSERT_TRUE(synced_utc_offset == 0xFFFF);
            ASSERT_SUCCESS(memcmp(&synced_time, &ts_zero, sizeof(ts_zero)));
        }
        else
        {

            /* Time synched. */
            ASSERT_TRUE(synced_utc_offset == expected_utc_offset);
            ASSERT_SUCCESS(memcmp(&synced_time, &expected_time, sizeof(expected_time)));
        }

        if (i == 0)
        {

            /* Wake up server thread to send another Announce.  */
            tx_thread_resume(&ntest_1);

            /* Sleep 5 seconds for sync up. */
            tx_thread_sleep(NX_IP_PERIODIC_RATE);

            ASSERT_TRUE(synced_utc_offset == (expected_utc_offset + 1));

            /* Stop the PTP client */
            status = nx_ptp_client_stop(&ptp_client);
            ASSERT_SUCCESS(status);
        }
        else if (i == 1)
        {

            /* Sleep until timeout. */
            timeout_ticks = NX_PTP_CLIENT_ANNOUNCE_RECEIPT_TIMEOUT *
                            (1 << NX_PTP_CLIENT_LOG_ANNOUNCE_INTERVAL) *
                            NX_IP_PERIODIC_RATE;
            tx_thread_sleep(timeout_ticks);
        }
    }

    printf("SUCCESS!\n");
    test_control_return(0);
}

/* This thread acts as PTP server, accepting the connection. */
static void    ntest_1_entry(ULONG thread_input)
{
DELAY_REQUEST_CONTEXT context;
UINT status;
UINT i;

    create_socket(&ip_1, &generic_socket, PTP_GENERAL_UDP_PORT);
    create_socket(&ip_1, &event_socket, PTP_EVENT_UDP_PORT);

    for (i = 0; i < 3; i++)
    {

        /* Suspend current thread and wait for client thread. */
        tx_thread_suspend(tx_thread_identify());

        /* Sleep 1 second to wait for PTP thread running. */
        tx_thread_sleep(1 * NX_IP_PERIODIC_RATE);

        /* Send announce.  */
        send_announce(&generic_socket, &pool_0, expected_utc_offset);

        if (i == 1)
        {

            /* Change the clock identify. */
            set_clock_id((UCHAR *)"\x00\x11\x22\x33\x44\x55\x66\x77\x88\x99");
        }

        /* Send sync.  */
        send_sync(&event_socket, &pool_0, &sync_ts);

        /* Send follow up.  */
        send_follow_up(&generic_socket, &pool_0, &follow_up_ts);

        if (i == 1)
        {

            /* Wait for delay request.  */
            status = receive_delay_request(&event_socket, &context, NX_IP_PERIODIC_RATE);
            ASSERT_TRUE(status == NX_NO_PACKET);
            continue;
        }

        /* Wait for delay request.  */
        status = receive_delay_request(&event_socket, &context, NX_IP_PERIODIC_RATE);
        ASSERT_SUCCESS(status);

        /* Send delay response.  */
        send_delay_response(&generic_socket, &pool_0, &context, &delay_response_ts);

        if (i == 0)
        {

            /* Suspend current thread and wait for client thread. */
            tx_thread_suspend(tx_thread_identify());

            /* Send another Announce with different utc offset.  */
            send_announce(&generic_socket, &pool_0, expected_utc_offset + 1);
        }
    }
}